/**
 * 
 */
package cyclopean.world.zone;

import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.Set;

import org.apache.log4j.Logger;

import cyclopean.world.Controller;
import cyclopean.world.actor.Actor;

import javagame.core.main.Time;

// TODO Javadoc
/**
 * 
 *
 * @author Jaco van der Westhuizen
 */
public final class Zone
{
	// X is east, Y is north, Z is up
	private static final Logger logger = Logger.getLogger(Zone.class);

	public static final int SIZE_X = 64;
	public static final int SIZE_Y = 64;
	public static final int SIZE_Z = 64;
	public static final int PER_X = SIZE_Z;
	public static final int PER_Y = PER_X * SIZE_X;
	public static final int BLOCKS_COUNT = SIZE_X * SIZE_Y * SIZE_Z;

	final byte[] blocks = new byte[BLOCKS_COUNT];
	
	private final Set<Integer> changes = new HashSet<Integer>();
	private final Set<ZoneListener> listeners = new HashSet<ZoneListener>();

	private final Set<Actor> actors = new HashSet<Actor>();
	private final Set<Controller> ais = new HashSet<Controller>();

	public Zone()
	{
		for (int y = 0; y < SIZE_Y; y++)
		{
			for (int x = 0; x < SIZE_X; x++)
			{
				int i = PER_X * x + PER_Y * y;
				int alt = (int)(Math.random() * 60);
				
				for (int z = 0; z < SIZE_Z; z++)
				{
				    if (z < alt)
				        blocks[i] = BlockType.DIRT.id;
				    else
				        if (z == alt)
	                        blocks[i] = BlockType.GRASS.id;
			            else
	                        blocks[i] = BlockType.AIR.id;
					
					i++;
				}
			}
		}
	}

	int getBlockId(int x, int y, int z)
	{
		return z + (PER_X * x) + (PER_Y * y);
	}

	public boolean setBlockType(int id, byte type)
	{
		blocks[id] = type;
		changes.add(id);

		return true;
	}

    public void update(Time time, UpdateLevel level)
    {
    	if (level.compareTo(UpdateLevel.STATIC) <= 0)
    	{
    		// TODO Player-triggered events?
    	}

    	if (level.compareTo(UpdateLevel.DISTANT) <= 0)
    	{
    		// TODO Weather
    	}

    	if (level.compareTo(UpdateLevel.LIVE) <= 0)
    	{
    		for (Controller con : ais)
    		{
        		// Do AI
    			con.update();
    		}
    		
    		for (Actor actor : actors)
    		{
    			// TODO Physics and/or behaviours
    			actor.update();
    		}
    	}

    	if (changes.size() > 0)
    	{
    		for (ZoneListener l : listeners)
    		{
    			l.onChange(this, changes);
    		}

    		changes.clear();
    	}
    }
    
    static interface ZoneListener
    {
    	public void onInit(Zone zone);
    	public void onChange(Zone zone, Set<Integer> ids);
    }
    
    public static enum UpdateLevel
    {
    	LIVE,
    	DISTANT,
    	STATIC;
    }

	/**
     * @param zoneRenderer
     */
    public void addBlockChangeListener(ZoneListener listener)
    {
	    this.listeners.add(listener);
    }
    
    public static <T extends Actor> T spawn(Class<T> clazz, Position pos)
    {
    	// TODO Check for collisions
    	try
        {
	        Constructor<T> cons = clazz.getDeclaredConstructor(Position.class);
	        T actor = cons.newInstance(pos);

	        pos.zone.actors.add(actor);

	        return actor;
        }
        catch (Exception e)
        {
	        logger.error("Failed to spawn Actor of class '" + clazz.getCanonicalName() + "'.", e);
	        return null;
        }
    }
    
    public boolean addZoneListener(ZoneListener l)
    {
    	if (listeners.add(l))
    	{
    		l.onInit(this);
    		return true;
    	}
    	
    	return false;
    }
    
    public boolean removeZoneListener(ZoneListener l)
    {
    	return listeners.remove(l);
    }

	/**
     * @param writer
     */
    public void save(FileOutputStream writer)// throws IOException
    {
	    // TODO Auto-generated method stub
	    
    }
}
